package com.mybatis.jpa.plugin;

import com.baomidou.mybatisplus.annotation.TableName;
import com.mybatis.jpa.annotation.NestedSelect;
import com.mybatis.jpa.util.FieldReflectUtil;

import java.lang.reflect.Method;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.apache.ibatis.executor.resultset.DefaultResultSetHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.Configuration;

/**
 * @author svili
 **/
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {
    Statement.class})})
public class ResultTypePlugin implements Interceptor {

  @Override
  public Object intercept(Invocation invocation) throws Throwable {

    //判断是否是默认结果处理器
    if (invocation.getTarget() instanceof DefaultResultSetHandler) {
      DefaultResultSetHandler resultSetHandler = (DefaultResultSetHandler) invocation.getTarget();

      Object[] args = invocation.getArgs();
      Statement stmt = (Statement) args[0];

      //从resultSetHandler对象中取出MappedStatement对象
      MappedStatement mappedStatement = (MappedStatement) FieldReflectUtil
          .getFieldValue(resultSetHandler, "mappedStatement");

      //判断执行的方法是否包含注解
      String methodId  = mappedStatement.getId();
      if(!isNestedAnnotationByMethodStr(methodId)){
        return invocation.proceed();
      }
      List<ResultMap> resultMaps = mappedStatement.getResultMaps();

      //MappedStatement中的resultMaps不等于空时
      if (resultMaps != null && !resultMaps.isEmpty()) {
        ResultMap resultMap = resultMaps.get(0);
        //ResultMap中的ResultMappings不等于空时
       /* if (resultMap.getResultMappings() == null || resultMap.getResultMappings().isEmpty()) {*/
          // 获取返回值的类型是否具有Entity与Table两个注解
          if (resultMap.getType().isAnnotationPresent(Entity.class) || resultMap.getType()
              .isAnnotationPresent(Table.class)|| resultMap.getType()
                  .isAnnotationPresent(TableName.class)) {

            //根据配置文件调用swpper持有期起创建一个ResultMapSwapper对象
            ResultMapSwapper swapper = ResultMapSwapperHolder.getSwapper(mappedStatement.getConfiguration());

            //核心---调用，创建ResultMap
            ResultMap newResultMap = swapper.reloadResultMap(mappedStatement.getResource(), resultMap.getId(), resultMap.getType());

            //创建一个ResultMap集合并且将创建的好的ResultMap添加至该集合
            List<ResultMap> newResultMaps = new ArrayList<>();
            newResultMaps.add(newResultMap);

            //将新创建的ResultMaps设置到mappedStatement对象中
            FieldReflectUtil.setFieldValue(mappedStatement, "resultMaps", newResultMaps);

            // modify the resultMaps
            //将mappedStatements设置到resultSetHandler对象中
            FieldReflectUtil.setFieldValue(resultSetHandler, "mappedStatement", mappedStatement);
          }
        //}
      }
      return resultSetHandler.handleResultSets(stmt);
    }
    return invocation.proceed();
  }

  @Override
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  @Override
  public void setProperties(Properties properties) {

  }

  /**
   * Use static inner classes ensure thread safety
   */
  private static class ResultMapSwapperHolder {

    private static Map<String, ResultMapSwapper> swappers = new HashMap<>();

    static ResultMapSwapper getSwapper(Configuration configuration) {
      String id = configuration.getEnvironment().getId();
      if (!swappers.containsKey(id)) {
        swappers.put(id, new ResultMapSwapper(configuration));
      }
      return swappers.get(id);
    }
  }


  private boolean isNestedAnnotationByMethodStr(String methodUrl) throws ClassNotFoundException {
    int a = methodUrl.lastIndexOf(".");

    String classStr = methodUrl.substring(0,a);
    String methodStr = methodUrl.substring(a+1,methodUrl.length());

    Class clazz = Class.forName(classStr);
    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
      if(method.getName().equals(methodStr)){
        if(method.isAnnotationPresent(NestedSelect.class)){
          return true;
        }
      }
    }
    return false;
  }

}
